#!/usr/bin/env python3

#
# Copyright (c) 2020-2023 Raspberry Pi (Trading) Ltd.
#
# SPDX-License-Identifier: BSD-3-Clause
#

import argparse
import os
import shutil
from pathlib import Path
import sys
import subprocess
import platform
import shlex
import csv

# TODO: conditional import of tkinter if --gui option is used
try:
    import tkinter as tk
    from tkinter import \
        messagebox as mb, \
        filedialog as fd, \
        simpledialog as sd, \
        ttk

except ImportError:
    print("[\033[91mERROR\033[0m] tkinter module not found")
    sys.exit(1)


class ExitCodes:
    """Exit codes per reason; range starts at 0xA00 to avoid clashing with OS reserved exit codes"""
    SUCCESS = 0
    INVALID_PROJECT_PATH = -0xA01
    EXISTING_PROJECT_NO_OVERWRITE = -0xA02
    NO_COMPILER_FOUND = -0xA03
    NO_PROJECT_NAME = -0xA04
    PICO_SDK_NOT_FOUND = -0xA05

CMAKELIST_FILENAME = 'CMakeLists.txt'
CMAKECACHE_FILENAME = 'CMakeCache.txt'

COMPILER_NAME = 'arm-none-eabi-gcc'

VSCODE_LAUNCH_FILENAME = 'launch.json'
VSCODE_C_PROPERTIES_FILENAME = 'c_cpp_properties.json'
VSCODE_SETTINGS_FILENAME ='settings.json'
VSCODE_EXTENSIONS_FILENAME ='extensions.json'
VSCODE_FOLDER='.vscode'

CONFIG_UNSET="Not set"

# Standard libraries for all builds
# And any more to string below, space separator
STANDARD_LIBRARIES = 'pico_stdlib'

# Indexed on feature name, tuple contains the C file, the H file and the CMake project name for the feature. 
# Some lists may contain an extra/ancillary file needed for that feature
GUI_TEXT = 0
C_FILE = 1
H_FILE = 2
LIB_NAME = 3
ANCILLARY_FILE = 4

features_list = {
    'spi' :             ("SPI",             "spi.c",            "hardware/spi.h",       "hardware_spi"),
    'i2c' :             ("I2C interface",   "i2c.c",            "hardware/i2c.h",       "hardware_i2c"),
    'dma' :             ("DMA support",     "dma.c",            "hardware/dma.h",       "hardware_dma"),
    'pio' :             ("PIO interface",   "pio.c",            "hardware/pio.h",       "hardware_pio"),
    'interp' :          ("HW interpolation", "interp.c",        "hardware/interp.h",    "hardware_interp"),
    'timer' :           ("HW timer",        "timer.c",          "hardware/timer.h",     "hardware_timer"),
    'watch' :           ("HW watchdog",     "watch.c",          "hardware/watchdog.h",  "hardware_watchdog"),
    'clocks' :          ("HW clocks",       "clocks.c",         "hardware/clocks.h",    "hardware_clocks"),
}

picow_options_list = {
    'picow_none' :      ("None", "",                            "",    "",                                                                  ""),
    'picow_led' :       ("PicoW onboard LED", "",               "pico/cyw43_arch.h",    "pico_cyw43_arch_none",                             ""),
    'picow_poll' :      ("Polled lwIP",     "",                 "pico/cyw43_arch.h",    "pico_cyw43_arch_lwip_poll",                        "lwipopts.h"),
    'picow_background' :("Background lwIP", "",                 "pico/cyw43_arch.h",    "pico_cyw43_arch_lwip_threadsafe_background",       "lwipopts.h"),
#    'picow_freertos' :  ("Full lwIP (FreeRTOS)", "",            "pico/cyw43_arch.h",    "pico_cyw43_arch_lwip_sys_freertos",                "lwipopts.h"),
}

stdlib_examples_list = {
    'uart':     ("UART",                    "uart.c",           "hardware/uart.h",      "hardware_uart"),
    'gpio' :    ("GPIO interface",          "gpio.c",           "hardware/gpio.h",      "hardware_gpio"),
    'div' :     ("Low level HW Divider",    "divider.c",        "hardware/divider.h",   "hardware_divider")
}

debugger_list = ["SWD", "PicoProbe", "CMSIS-DAP Debug Probe"]
debugger_config_list = ["raspberrypi-swd.cfg", "picoprobe.cfg", "cmsis-dap.cfg"]
debug_server_args_list = ["", "", "\"-c\", \"adapter speed 5000\" "]

DEFINES = 0
INITIALISERS = 1
# Could add an extra item that shows how to use some of the available functions for the feature
#EXAMPLE = 2

# This also contains example code for the standard library (see stdlib_examples_list)
code_fragments_per_feature = {
    'uart' : [
               ("// UART defines",
                "// By default the stdout UART is `uart0`, so we will use the second one",
                "#define UART_ID uart1",
                "#define BAUD_RATE 9600", "",
                "// Use pins 4 and 5 for UART1",
                "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
                "#define UART_TX_PIN 4",
                "#define UART_RX_PIN 5" ),

               ( "// Set up our UART",
                 "uart_init(UART_ID, BAUD_RATE);",
                 "// Set the TX and RX pins by using the function select on the GPIO",
                 "// Set datasheet for more information on function select",
                 "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);",
                 "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);", "" )
            ],
    'spi' : [
              ( "// SPI Defines",
                "// We are going to use SPI 0, and allocate it to the following GPIO pins",
                "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
                "#define SPI_PORT spi0",
                "#define PIN_MISO 16",
                "#define PIN_CS   17",
                "#define PIN_SCK  18",
                "#define PIN_MOSI 19" ),

              ( "// SPI initialisation. This example will use SPI at 1MHz.",
                "spi_init(SPI_PORT, 1000*1000);",
                "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);",
                "gpio_set_function(PIN_CS,   GPIO_FUNC_SIO);",
                "gpio_set_function(PIN_SCK,  GPIO_FUNC_SPI);",
                "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);", "",
                "// Chip select is active-low, so we'll initialise it to a driven-high state",
                "gpio_set_dir(PIN_CS, GPIO_OUT);",
                "gpio_put(PIN_CS, 1);", "")
            ],
    'i2c' : [
              (
                "// I2C defines",
                "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.",
                "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
                "#define I2C_PORT i2c0",
                "#define I2C_SDA 8",
                "#define I2C_SCL 9",
              ),
              (
                "// I2C Initialisation. Using it at 400Khz.",
                "i2c_init(I2C_PORT, 400*1000);","",
                "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);",
                "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);",
                "gpio_pull_up(I2C_SDA);",
                "gpio_pull_up(I2C_SCL);"
              )
            ],
    "gpio" : [
              (
                "// GPIO defines",
                "// Example uses GPIO 2",
                "#define GPIO 2"
              ),
              (
                "// GPIO initialisation.",
                "// We will make this GPIO an input, and pull it up by default",
                "gpio_init(GPIO);",
                "gpio_set_dir(GPIO, GPIO_IN);",
                "gpio_pull_up(GPIO);","",
              )
            ],
    "interp" :[
               (),
               (
                "// Interpolator example code",
                "interp_config cfg = interp_default_config();",
                "// Now use the various interpolator library functions for your use case",
                "// e.g. interp_config_clamp(&cfg, true);",
                "//      interp_config_shift(&cfg, 2);",
                "// Then set the config ",
                "interp_set_config(interp0, 0, &cfg);",
               )
              ],

    "timer"  : [
                (
                 "int64_t alarm_callback(alarm_id_t id, void *user_data) {",
                 "    // Put your timeout handler code in here",
                 "    return 0;",
                 "}"
                ),
                (
                 "// Timer example code - This example fires off the callback after 2000ms",
                 "add_alarm_in_ms(2000, alarm_callback, NULL, false);"
                )
              ],

    "watchdog":[ (),
                (
                    "// Watchdog example code",
                    "if (watchdog_caused_reboot()) {",
                    "    // Whatever action you may take if a watchdog caused a reboot",
                    "}","",
                    "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot",
                    "// second arg is pause on debug which means the watchdog will pause when stepping through code",
                    "watchdog_enable(100, 1);","",
                    "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot"
                    "watchdog_update();",
                )
              ],

    "div"    : [ (),
                 (
                    "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ",
                    "// over the divider (and support for 64 bit divides), and of course by default regular C language integer",
                    "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and",
                    "// gain the benefits of the fast hardware divider.",
                    "int32_t dividend = 123456;",
                    "int32_t divisor = -321;",
                    "// This is the recommended signed fast divider for general use.",
                    "divmod_result_t result = hw_divider_divmod_s32(dividend, divisor);",
                    "printf(\"%d/%d = %d remainder %d\\n\", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result));",
                    "// This is the recommended unsigned fast divider for general use.",
                    "int32_t udividend = 123456;",
                    "int32_t udivisor = 321;",
                    "divmod_result_t uresult = hw_divider_divmod_u32(udividend, udivisor);",
                    "printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult));"
                 )
                ]
}

configuration_dictionary = list(dict())

isMac = False
isWindows = False
compilerPath = Path("/usr/bin/arm-none-eabi-gcc")

def GetBackground():
    return 'white'

def GetButtonBackground():
    return 'white'

def GetTextColour():
    return 'black'

def GetButtonTextColour():
    return '#c51a4a'

def RunGUI(sdkpath, args):
    root = tk.Tk()
    style = ttk.Style(root)
    style.theme_use('default')

    ttk.Style().configure("TButton", padding=6, relief="groove", border=2, foreground=GetButtonTextColour(), background=GetButtonBackground())
    ttk.Style().configure("TLabel", foreground=GetTextColour(), background=GetBackground() )
    ttk.Style().configure("TCheckbutton", foreground=GetTextColour(), background=GetBackground())
    ttk.Style().configure("TRadiobutton", foreground=GetTextColour(), background=GetBackground() )
    ttk.Style().configure("TLabelframe", foreground=GetTextColour(), background=GetBackground() )
    ttk.Style().configure("TLabelframe.Label", foreground=GetTextColour(), background=GetBackground() )
    ttk.Style().configure("TCombobox", foreground=GetTextColour(), background=GetBackground() )
    ttk.Style().configure("TListbox", foreground=GetTextColour(), background=GetBackground() )

    ttk.Style().map("TCheckbutton", background = [('disabled', GetBackground())])
    ttk.Style().map("TRadiobutton", background = [('disabled', GetBackground())])
    ttk.Style().map("TButton", background = [('disabled', GetBackground())])
    ttk.Style().map("TLabel", background = [('background', GetBackground())])

    app = ProjectWindow(root, sdkpath, args)

    app.configure(background=GetBackground())

    root.mainloop()
    sys.exit(ExitCodes.SUCCESS)

def RunWarning(message):
    mb.showwarning('Raspberry Pi Pico Project Generator', message)
    sys.exit(ExitCodes.SUCCESS)

import threading

def thread_function(text, command, ok):
    l = shlex.split(command)
    proc = subprocess.Popen(l, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    for line in iter(proc.stdout.readline,''):
        if not line:
            if ok:
                ok["state"] = tk.NORMAL
            return
        text.insert(tk.END, line)
        text.see(tk.END)

# Function to run an OS command and display the output in a new modal window
class DisplayWindow(tk.Toplevel):
    def __init__(self, parent, title):
        tk.Toplevel.__init__(self, parent)
        self.parent = parent
        self.init_window(title)

    def init_window(self, title):
        self.title(title)

        frame = tk.Frame(self, borderwidth=5, relief=tk.RIDGE)
        frame.pack(fill=tk.X, expand=True, side=tk.TOP)

        scrollbar = tk.Scrollbar(frame)
        self.text = tk.Text(frame, bg='gray14', fg='gray99')
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.text.pack(side=tk.LEFT, fill=tk.Y)
        scrollbar.config(command=self.text.yview)
        self.text.config(yscrollcommand=scrollbar.set)

        frame1 = tk.Frame(self, borderwidth=1)
        frame1.pack(fill=tk.X, expand=True, side=tk.BOTTOM)
        self.OKButton = ttk.Button(frame1, text="OK", command=self.OK)
        self.OKButton["state"] = tk.DISABLED
        self.OKButton.pack()

        # make dialog modal
        self.transient(self.parent)
        self.grab_set()

    def OK(self):
        self.grab_release()
        self.destroy()

def RunCommandInWindow(parent, command):
    w = DisplayWindow(parent, command)
    x = threading.Thread(target=thread_function, args=(w.text, command, w.OKButton))
    x.start()
    parent.wait_window(w)

class EditBoolWindow(sd.Dialog):

    def __init__(self, parent, configitem, current):
        self.parent = parent
        self.config_item = configitem
        self.current = current
        sd.Dialog.__init__(self, parent, "Edit boolean configuration")


    def body(self, master):
        self.configure(background=GetBackground())
        ttk.Label(self, text=self.config_item['name']).pack()
        self.result = tk.StringVar()
        self.result.set(self.current)
        ttk.Radiobutton(master, text="True", variable=self.result, value="True").pack(anchor=tk.W)
        ttk.Radiobutton(master, text="False", variable=self.result, value="False").pack(anchor=tk.W)
        ttk.Radiobutton(master, text=CONFIG_UNSET, variable=self.result, value=CONFIG_UNSET).pack(anchor=tk.W)

    def get(self):
        return self.result.get()

class EditIntWindow(sd.Dialog):

    def __init__(self, parent, configitem, current):
        self.parent = parent
        self.config_item = configitem
        self.current = current
        sd.Dialog.__init__(self, parent, "Edit integer configuration")

    def body(self, master):
        self.configure(background=GetBackground())
        str = self.config_item['name'] + "  Max = " + self.config_item['max'] + "  Min = " + self.config_item['min']
        ttk.Label(self, text=str).pack()
        self.input =  tk.Entry(self)
        self.input.pack(pady=4)
        self.input.insert(0, self.current)
        ttk.Button(self, text=CONFIG_UNSET, command=self.unset).pack(pady=5)

    def validate(self):
        self.result = self.input.get()
        # Check for numeric entry
        return True

    def unset(self):
        self.result = CONFIG_UNSET
        self.destroy()

    def get(self):
        return self.result

class EditEnumWindow(sd.Dialog):
    def __init__(self, parent, configitem, current):
        self.parent = parent
        self.config_item = configitem
        self.current = current
        sd.Dialog.__init__(self, parent, "Edit Enumeration configuration")

    def body(self, master):
        #self.configure(background=GetBackground())
        values = self.config_item['enumvalues'].split('|')
        values.insert(0,'Not set')
        self.input =  ttk.Combobox(self, values=values, state='readonly')
        self.input.set(self.current)
        self.input.pack(pady=12)

    def validate(self):
        self.result = self.input.get()
        return True

    def get(self):
        return self.result


class ConfigurationWindow(tk.Toplevel):

    def __init__(self, parent, initial_config):
        tk.Toplevel.__init__(self, parent)
        self.master = parent
        self.results = initial_config
        self.init_window(self)

    def init_window(self, args):
        self.configure(background=GetBackground())
        self.title("Advanced Configuration")
        ttk.Label(self, text="Select the advanced options you wish to enable or change. Note that you really should understand the implications of changing these items before using them!").grid(row=0, column=0, columnspan=5)
        ttk.Label(self, text="Name").grid(row=1, column=0, sticky=tk.W)
        ttk.Label(self, text="Type").grid(row=1, column=1, sticky=tk.W)
        ttk.Label(self, text="Min").grid(row=1, column=2, sticky=tk.W)
        ttk.Label(self, text="Max").grid(row=1, column=3, sticky=tk.W)
        ttk.Label(self, text="Default").grid(row=1, column=4, sticky=tk.W)
        ttk.Label(self, text="User").grid(row=1, column=5, sticky=tk.W)

        okButton = ttk.Button(self, text="OK", command=self.ok)
        cancelButton = ttk.Button(self, text="Cancel", command=self.cancel)

        self.namelist = tk.Listbox(self, selectmode=tk.SINGLE)
        self.typelist = tk.Listbox(self, selectmode=tk.SINGLE)
        self.minlist = tk.Listbox(self, selectmode=tk.SINGLE)
        self.maxlist = tk.Listbox(self, selectmode=tk.SINGLE)
        self.defaultlist = tk.Listbox(self, selectmode=tk.SINGLE)
        self.valuelist = tk.Listbox(self, selectmode=tk.SINGLE)

        self.descriptionText = tk.Text(self, state=tk.DISABLED, height=2)

        ## Make a list of our list boxes to make it all easier to handle
        self.listlist = [self.namelist, self.typelist, self.minlist, self.maxlist, self.defaultlist, self.valuelist]

        scroll = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.yview)

        for box in self.listlist:
            box.config(width=0)
            box.config(yscrollcommand=scroll.set)
            box.bind("<MouseWheel>", self.mousewheel)
            box.bind("<Button-4>", self.mousewheel)
            box.bind("<Button-5>", self.mousewheel)
            box.bind("<<ListboxSelect>>", self.changeSelection)
            box.bind("<Double-Button>", self.doubleClick)
            box.config(exportselection=False)
            box.bind("<Down>", self.OnEntryUpDown)
            box.bind("<Up>", self.OnEntryUpDown)

        scroll.grid(column=7, sticky=tk.N + tk.S)

        i = 0
        for box in self.listlist:
            box.grid(row=2, column=i, padx=0, sticky=tk.W + tk.E)
            i+=1

        self.descriptionText.grid(row = 3, column=0, columnspan=4, sticky=tk.W + tk.E)
        cancelButton.grid(column=5, row = 3, padx=5)
        okButton.grid(column=4, row = 3, sticky=tk.E, padx=5)

        # populate the list box with our config options
        for conf in configuration_dictionary:
            self.namelist.insert(tk.END, conf['name'])
            s = conf['type']
            if s == "":
                s = "int"
            self.typelist.insert(tk.END, s)
            self.maxlist.insert(tk.END, conf['max'])
            self.minlist.insert(tk.END, conf['min'])
            self.defaultlist.insert(tk.END, conf['default'])

            # see if this config has a setting, our results member has this predefined from init
            val = self.results.get(conf['name'], CONFIG_UNSET)
            self.valuelist.insert(tk.END, val)
            if val != CONFIG_UNSET:
                self.valuelist.itemconfig(self.valuelist.size() - 1, {'bg':'green'})

    def yview(self, *args):
        for box in self.listlist:
            box.yview(*args)

    def mousewheel(self, event):
        if (event.num == 4):    # Linux encodes wheel as 'buttons' 4 and 5
            delta = -1
        elif (event.num == 5):
            delta = 1
        else:                   # Windows & OSX
            delta = event.delta

        for box in self.listlist:
            box.yview("scroll", delta, "units")
        return "break"

    def changeSelection(self, evt):
        box = evt.widget
        sellist = box.curselection()

        if sellist:
            index = int(sellist[0])
            config = self.namelist.get(index)
            # Now find the description for that config in the dictionary
            for conf in configuration_dictionary:
                if conf['name'] == config:
                    self.descriptionText.config(state=tk.NORMAL)
                    self.descriptionText.delete(1.0,tk.END)
                    str = config + "\n" + conf['description']
                    self.descriptionText.insert(1.0, str)
                    self.descriptionText.config(state=tk.DISABLED)
                    break
            # Set all the other list boxes to the same index
            for b in self.listlist:
                if b != box:
                    b.selection_clear(0, tk.END)
                    b.selection_set(index)

    def OnEntryUpDown(self, event):
        box = event.widget
        selection = box.curselection()

        if selection:
            index = int(selection[0])
            if event.keysym == 'Up':
                index -= 1
            elif event.keysym == 'Down':
                index += 1

            if 0 <= index < box.size():
                for b in self.listlist:
                        b.selection_clear(0, tk.END)
                        b.selection_set(index)
                        b.see(index)

    def doubleClick(self, evt):
        box = evt.widget
        index = int(box.curselection()[0])
        config = self.namelist.get(index)
        # Get the associated dict entry from our list of configs
        for conf in configuration_dictionary:
            if conf['name'] == config:
                if (conf['type'] == 'bool'):
                    result = EditBoolWindow(self, conf, self.valuelist.get(index)).get()
                elif (conf['type'] == 'int' or conf['type'] == ""): # "" defaults to int
                    result = EditIntWindow(self, conf, self.valuelist.get(index)).get()
                elif conf['type'] == 'enum':
                    result = EditEnumWindow(self, conf, self.valuelist.get(index)).get()

                # Update the valuelist with our new item
                self.valuelist.delete(index)
                self.valuelist.insert(index, result)
                if result != CONFIG_UNSET:
                    self.valuelist.itemconfig(index, {'bg':'green'})
                break

    def ok(self):
        # Get the selections, and create a list of them
        for i, val in enumerate(self.valuelist.get(0, tk.END)):
            if val != CONFIG_UNSET:
                self.results[self.namelist.get(i)] = val
            else:
                self.results.pop(self.namelist.get(i), None)

        self.destroy()

    def cancel(self):
        self.destroy()

    def get(self):
        return self.results

class WirelessSettingsWindow(sd.Dialog):

    def __init__(self, parent):
        sd.Dialog.__init__(self, parent, "Wireless settings")
        self.parent = parent

    def body(self, master):
        self.configure(background=GetBackground())
        master.configure(background=GetBackground())
        self.ssid = tk.StringVar()
        self.password = tk.StringVar()

        a = ttk.Label(master, text='SSID :', background=GetBackground())
        a.grid(row=0, column=0, sticky=tk.E)
        a.configure(background=GetBackground())
        ttk.Entry(master, textvariable=self.ssid).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5)

        ttk.Label(master, text='Password :').grid(row=1, column=0, sticky=tk.E)
        ttk.Entry(master, textvariable=self.password).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5)

        self.transient(self.parent)
        self.grab_set()

    def ok(self):
        self.grab_release()
        self.destroy()

    def cancel(self):
        self.destroy()

    def get(self):
        return (self.ssid.get(), self.password.get())

# Our main window
class ProjectWindow(tk.Frame):

    def __init__(self, parent, sdkpath, args):
        tk.Frame.__init__(self, parent)
        self.master = parent
        self.sdkpath = sdkpath
        self.init_window(args)
        self.configs = dict()
        self.ssid = str()
        self.password = str()

    def setState(self, thing, state):
        for child in thing.winfo_children():
            child.configure(state=state)

    def boardtype_change_callback(self, event):
        boardtype = self.boardtype.get()
        if boardtype == "pico_w":
            self.setState(self.picowSubframe, "enabled")
        else:
            self.setState(self.picowSubframe, "disabled")

    def wirelessSettings(self):
        result = WirelessSettingsWindow(self)
        self.ssid, self.password = result.get()

    def init_window(self, args):
        self.master.title("Raspberry Pi Pico Project Generator")
        self.master.configure(bg=GetBackground())

        optionsRow = 0

        mainFrame = tk.Frame(self, bg=GetBackground()).grid(row=optionsRow, column=0, columnspan=6, rowspan=12)

        # Need to keep a reference to the image or it will not appear.
        self.logo = tk.PhotoImage(file=GetFilePath("logo_alpha.gif"))
        logowidget = ttk.Label(mainFrame, image=self.logo, borderwidth=0, relief="solid").grid(row=0,column=0, columnspan=5, pady=10)

        optionsRow += 2

        namelbl = ttk.Label(mainFrame, text='Project Name :').grid(row=optionsRow, column=0, sticky=tk.E)
        self.projectName = tk.StringVar()

        if args.name != None:
            self.projectName.set(args.name)
        else:
            self.projectName.set('ProjectName')

        nameEntry = ttk.Entry(mainFrame, textvariable=self.projectName).grid(row=optionsRow, column=1, sticky=tk.W+tk.E, padx=5)

        optionsRow += 1

        locationlbl = ttk.Label(mainFrame, text='Location :').grid(row=optionsRow, column=0, sticky=tk.E)
        self.locationName = tk.StringVar()
        self.locationName.set(os.getcwd() if not args.projectRoot else args.projectRoot)
        locationEntry = ttk.Entry(mainFrame, textvariable=self.locationName).grid(row=optionsRow, column=1, columnspan=3, sticky=tk.W+tk.E, padx=5)
        locationBrowse = ttk.Button(mainFrame, text='Browse', command=self.browse).grid(row=3, column=4)

        optionsRow += 1

        ttk.Label(mainFrame, text = "Board Type :").grid(row=optionsRow, column=0, padx=4, sticky=tk.E)
        self.boardtype = ttk.Combobox(mainFrame, values=boardtype_list, )
        self.boardtype.grid(row=4, column=1, padx=4, sticky=tk.W+tk.E)
        self.boardtype.set('pico')
        self.boardtype.bind('<<ComboboxSelected>>',self.boardtype_change_callback)
        optionsRow += 1

        # Features section
        featuresframe = ttk.LabelFrame(mainFrame, text="Library Options", relief=tk.RIDGE, borderwidth=2)
        featuresframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=5, ipadx=5, padx=5, pady=5, sticky=tk.E+tk.W)

        s = (len(features_list)/3)

        self.feature_checkbox_vars = []
        row = 0
        col = 0
        for i in features_list:
            var = tk.StringVar(value='') # Off by default for the moment
            c = features_list[i][GUI_TEXT]
            cb = ttk.Checkbutton(featuresframe, text = c, var=var, onvalue=i, offvalue='')
            cb.grid(row=row, column=col, padx=15, pady=2, ipadx=1, ipady=1, sticky=tk.E+tk.W)
            self.feature_checkbox_vars.append(var)
            row+=1
            if row >= s:
                col+=1
                row = 0

        optionsRow += 5

        # PicoW options section
        self.picowSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Pico Wireless Options")
        self.picowSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
        self.pico_wireless = tk.StringVar()

        col = 0
        row = 0
        for i in picow_options_list:
            rb = ttk.Radiobutton(self.picowSubframe, text=picow_options_list[i][GUI_TEXT], variable=self.pico_wireless, val=i)
            rb.grid(row=row, column=col,  padx=15, pady=1, sticky=tk.E+tk.W)
            col+=1
            if col == 3:
                col=0
                row+=1

        # DOnt actually need any settings at the moment.
        # ttk.Button(self.picowSubframe, text='Settings', command=self.wirelessSettings).grid(row=0, column=4, padx=5, pady=2, sticky=tk.E)

        self.setState(self.picowSubframe, "disabled")

        optionsRow += 3

        # output options section
        ooptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Console Options")
        ooptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)

        self.wantUART = tk.IntVar()
        self.wantUART.set(args.uart)
        ttk.Checkbutton(ooptionsSubframe, text="Console over UART", variable=self.wantUART).grid(row=0, column=0, padx=4, sticky=tk.W)

        self.wantUSB = tk.IntVar()
        self.wantUSB.set(args.usb)
        ttk.Checkbutton(ooptionsSubframe, text="Console over USB (Disables other USB use)", variable=self.wantUSB).grid(row=0, column=1, padx=4, sticky=tk.W)

        optionsRow += 2

        # Code options section
        coptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Code Options")
        coptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=3, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)

        self.wantExamples = tk.IntVar()
        self.wantExamples.set(args.examples)
        ttk.Checkbutton(coptionsSubframe, text="Add examples for Pico library", variable=self.wantExamples).grid(row=0, column=0, padx=4, sticky=tk.W)

        self.wantRunFromRAM = tk.IntVar()
        self.wantRunFromRAM.set(args.runFromRAM)
        ttk.Checkbutton(coptionsSubframe, text="Run from RAM", variable=self.wantRunFromRAM).grid(row=0, column=1, padx=4, sticky=tk.W)

        self.wantCPP = tk.IntVar()
        self.wantCPP.set(args.cpp)
        ttk.Checkbutton(coptionsSubframe, text="Generate C++", variable=self.wantCPP).grid(row=0, column=3, padx=4, sticky=tk.W)

        ttk.Button(coptionsSubframe, text="Advanced...", command=self.config).grid(row=0, column=4, sticky=tk.E)

        self.wantCPPExceptions = tk.IntVar()
        self.wantCPPExceptions.set(args.cppexceptions)
        ttk.Checkbutton(coptionsSubframe, text="Enable C++ exceptions", variable=self.wantCPPExceptions).grid(row=1, column=0, padx=4, sticky=tk.W)

        self.wantCPPRTTI = tk.IntVar()
        self.wantCPPRTTI.set(args.cpprtti)
        ttk.Checkbutton(coptionsSubframe, text="Enable C++ RTTI", variable=self.wantCPPRTTI).grid(row=1, column=1, padx=4, sticky=tk.W)

        optionsRow += 3

        # Build Options section

        boptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Build Options")
        boptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)

        self.wantBuild = tk.IntVar()
        self.wantBuild.set(args.build)
        ttk.Checkbutton(boptionsSubframe, text="Run build after generation", variable=self.wantBuild).grid(row=0, column=0, padx=4, sticky=tk.W)

        self.wantOverwrite = tk.IntVar()
        self.wantOverwrite.set(args.overwrite)
        ttk.Checkbutton(boptionsSubframe, text="Overwrite existing projects", variable=self.wantOverwrite).grid(row=0, column=1, padx=4, sticky=tk.W)

        optionsRow += 2
        
        # IDE Options section

        vscodeoptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="IDE Options")
        vscodeoptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)

        self.wantVSCode = tk.IntVar()
        if args.project is None:
            self.wantVSCode.set(False)
        else:
            self.wantVSCode.set('vscode' in args.project)
        ttk.Checkbutton(vscodeoptionsSubframe, text="Create VSCode project", variable=self.wantVSCode).grid(row=0, column=0, padx=4, sticky=tk.W)

        ttk.Label(vscodeoptionsSubframe, text = "     Debugger:").grid(row=0, column=1, padx=4, sticky=tk.W)

        self.debugger = ttk.Combobox(vscodeoptionsSubframe, values=debugger_list, state="readonly")
        self.debugger.grid(row=0, column=2, padx=4, sticky=tk.W)
        self.debugger.current(args.debugger)

        optionsRow += 2

        # OK, Cancel, Help section
        # creating buttons
        QuitButton = ttk.Button(mainFrame, text="Quit", command=self.quit).grid(row=optionsRow, column=4, stick=tk.E, padx=10, pady=5)
        OKButton = ttk.Button(mainFrame, text="OK", command=self.OK).grid(row=optionsRow, column=3, padx=4, pady=5, sticky=tk.E)

        # TODO help not implemented yet
        # HelpButton = ttk.Button(mainFrame, text="Help", command=self.help).grid(row=optionsRow, column=0, pady=5)

        # You can set a default path here, replace the string with whereever you want.
        # self.locationName.set('/home/pi/pico_projects')

    def GetFeatures(self):
        features = []

        i = 0
        for cb in self.feature_checkbox_vars:
            s = cb.get()
            if s != '':
                features.append(s)

        picow_extra = self.pico_wireless.get()

        if picow_extra != 'picow_none':
            features.append(picow_extra)

        return features

    def quit(self):
        # TODO Check if we want to exit here
        sys.exit(ExitCodes.SUCCESS)

    def OK(self):
        # OK, grab all the settings from the page, then call the generators
        projectPath = self.locationName.get()
        features = self.GetFeatures()
        projects = list()
        if (self.wantVSCode.get()):
            projects.append("vscode")

        params={
                'sdkPath'       : self.sdkpath,
                'projectRoot'   : Path(projectPath),
                'projectName'   : self.projectName.get(),
                'wantGUI'       : True,
                'wantOverwrite' : self.wantOverwrite.get(),
                'wantBuild'     : self.wantBuild.get(),
                'boardtype'     : self.boardtype.get(),
                'features'      : features,
                'projects'      : projects,
                'configs'       : self.configs,
                'wantRunFromRAM': self.wantRunFromRAM.get(),
                'wantExamples'  : self.wantExamples.get(),
                'wantUART'      : self.wantUART.get(),
                'wantUSB'       : self.wantUSB.get(),
                'wantCPP'       : self.wantCPP.get(),
                'debugger'      : self.debugger.current(),
                'exceptions'    : self.wantCPPExceptions.get(),
                'rtti'          : self.wantCPPRTTI.get(),
                'ssid'          : self.ssid,
                'password'      : self.password,
                }

        DoEverything(self, params)

    def browse(self):
        name = fd.askdirectory()
        self.locationName.set(name)

    def help(self):
        print("Help TODO")

    def config(self):
        # Run the configuration window
        self.configs = ConfigurationWindow(self, self.configs).get()

def CheckPrerequisites():
    global isMac, isWindows
    isMac = (platform.system() == 'Darwin')
    isWindows = (platform.system() == 'Windows')

    # Do we have a compiler?
    return shutil.which(COMPILER_NAME)


def CheckSDKPath(gui):
    sdkPath = os.getenv('PICO_SDK_PATH')

    if sdkPath == None:
        m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH is not set'
        if (gui):
            RunWarning(m)
        else:
            print(m)
    elif not os.path.isdir(sdkPath):
        m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH does not point to a directory'
        if (gui):
            RunWarning(m)
        else:
            print(m)
        sdkPath = None

    return sdkPath

def GetFilePath(filename):
    if os.path.islink(__file__):
        script_file = os.readlink(__file__)
    else:
        script_file = __file__
    return os.path.join(os.path.dirname(script_file), filename)

def ParseCommandLine():
    debugger_flags = ', '.join('{} = {}'.format(i, v) for i, v in enumerate(debugger_list))
    parser = argparse.ArgumentParser(description='Pico Project generator')
    parser.add_argument("name", nargs="?", help="Name of the project")
    parser.add_argument("-t", "--tsv", help="Select an alternative pico_configs.tsv file", default=GetFilePath("pico_configs.tsv"))
    parser.add_argument("-o", "--output", help="Set an alternative CMakeList.txt filename", default="CMakeLists.txt")
    parser.add_argument("-x", "--examples", action='store_true', help="Add example code for the Pico standard library")
    parser.add_argument("-l", "--list", action='store_true', help="List available features")
    parser.add_argument("-c", "--configs", action='store_true', help="List available project configuration items")
    parser.add_argument("-f", "--feature", action='append', help="Add feature to generated project")
    parser.add_argument("-over", "--overwrite", action='store_true', help="Overwrite any existing project AND files")
    parser.add_argument("-b", "--build", action='store_true', help="Build after project created")
    parser.add_argument("-g", "--gui", action='store_true', help="Run a GUI version of the project generator")
    parser.add_argument("-p", "--project", action='append', help="Generate projects files for IDE. Options are: vscode")
    parser.add_argument("-r", "--runFromRAM", action='store_true', help="Run the program from RAM rather than flash")
    parser.add_argument("-uart", "--uart", action='store_true', default=1, help="Console output to UART (default)")
    parser.add_argument("-nouart", "--nouart", action='store_true', default=0, help="Disable console output to UART")
    parser.add_argument("-usb", "--usb", action='store_true', help="Console output to USB (disables other USB functionality")
    parser.add_argument("-cpp", "--cpp", action='store_true', default=0, help="Generate C++ code")
    parser.add_argument("-cpprtti", "--cpprtti", action='store_true', default=0, help="Enable C++ RTTI (Uses more memory)")
    parser.add_argument("-cppex", "--cppexceptions", action='store_true', default=0, help="Enable C++ exceptions (Uses more memory)")
    parser.add_argument("-d", "--debugger", type=int, help="Select debugger ({})".format(debugger_flags), default=0)
    parser.add_argument("-board", "--boardtype", action="store", default='pico', help="Select board type (see --boardlist for available boards)")
    parser.add_argument("-bl", "--boardlist", action="store_true", help="List available board types")
    parser.add_argument("-cp", "--cpath", help="Override default VSCode compiler path")
    parser.add_argument("-root", "--projectRoot", help="Override default project root where the new project will be created")

    return parser.parse_args()


def GenerateMain(folder, projectName, features, cpp):

    if cpp:
        filename = Path(folder) / (projectName + '.cpp')
    else:
        filename = Path(folder) / (projectName + '.c')

    file = open(filename, 'w')

    main = ('#include <stdio.h>\n'
            '#include "pico/stdlib.h"\n'
            )
    file.write(main)

    if (features):

        # Add any includes
        for feat in features:
            if (feat in features_list):
                o = f'#include "{features_list[feat][H_FILE]}"\n'
                file.write(o)
            if (feat in stdlib_examples_list):
                o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n'
                file.write(o)
            if (feat in picow_options_list):
                o = f'#include "{picow_options_list[feat][H_FILE]}"\n'
                file.write(o)

        file.write('\n')

        # Add any defines
        for feat in features:
            if (feat in code_fragments_per_feature):
                for s in code_fragments_per_feature[feat][DEFINES]:
                    file.write(s)
                    file.write('\n')
                file.write('\n')

    main = ('\n\n'
            'int main()\n'
            '{\n'
            '    stdio_init_all();\n\n'
            )

    if (features):
        # Add any initialisers
        indent = 4
        for feat in features:
            if (feat in code_fragments_per_feature):
                for s in code_fragments_per_feature[feat][INITIALISERS]:
                    main += (" " * indent)
                    main += s
                    main += '\n'
            main += '\n'

    main += ('    puts("Hello, world!");\n\n'
             '    return 0;\n'
             '}\n'
            )

    file.write(main)

    file.close()


def GenerateCMake(folder, params):
   
    filename = Path(folder) / CMAKELIST_FILENAME
    projectName = params['projectName']
    board_type = params['boardtype']

    # OK, for the path, CMake will accept forward slashes on Windows, and thats
    # seemingly a bit easier to handle than the backslashes
    p = str(params['sdkPath']).replace('\\','/')
    sdk_path = f'"{p}"'

    cmake_header1 = (f"# Generated Cmake Pico project file\n\n"
                 "cmake_minimum_required(VERSION 3.13)\n\n"
                 "set(CMAKE_C_STANDARD 11)\n"
                 "set(CMAKE_CXX_STANDARD 17)\n\n"
                 "# Initialise pico_sdk from installed location\n"
                 "# (note this can come from environment, CMake cache etc)\n"
                 f"set(PICO_SDK_PATH {sdk_path})\n\n"
                 f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n"
                 "# Pull in Raspberry Pi Pico SDK (must be before project)\n"
                 "include(pico_sdk_import.cmake)\n\n"
                 "if (PICO_SDK_VERSION_STRING VERSION_LESS \"1.4.0\")\n"
                 "  message(FATAL_ERROR \"Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}\")\n"
                 "endif()\n\n"
                 f"project({projectName} C CXX ASM)\n"
                )
    
    cmake_header3 = (
                "\n# Initialise the Raspberry Pi Pico SDK\n"
                "pico_sdk_init()\n\n"
                "# Add executable. Default name is the project name, version 0.1\n\n"
                )


    file = open(filename, 'w')

    file.write(cmake_header1)

    if params['exceptions']:
        file.write("\nset(PICO_CXX_ENABLE_EXCEPTIONS 1)\n")

    if params['rtti']:
        file.write("\nset(PICO_CXX_ENABLE_RTTI 1)\n")

    file.write(cmake_header3)

    # add the preprocessor defines for overall configuration
    if params['configs']:
        file.write('# Add any PICO_CONFIG entries specified in the Advanced settings\n')
        for c, v in params['configs'].items():
            if v == "True":
                v = "1"
            elif v == "False":
                v = "0"
            file.write(f'add_compile_definitions({c} = {v})\n')
        file.write('\n')

    # No GUI/command line to set a different executable name at this stage
    executableName = projectName

    if params['wantCPP']:
        file.write(f'add_executable({projectName} {projectName}.cpp )\n\n')
    else:
        file.write(f'add_executable({projectName} {projectName}.c )\n\n')

    file.write(f'pico_set_program_name({projectName} "{executableName}")\n')
    file.write(f'pico_set_program_version({projectName} "0.1")\n\n')

    if params['wantRunFromRAM']:
        file.write(f'# no_flash means the target is to run from RAM\n')
        file.write(f'pico_set_binary_type({projectName} no_flash)\n\n')

    # Console output destinations
    if params['wantUART']:
        file.write(f'pico_enable_stdio_uart({projectName} 1)\n')
    else:
        file.write(f'pico_enable_stdio_uart({projectName} 0)\n')

    if params['wantUSB']:
        file.write(f'pico_enable_stdio_usb({projectName} 1)\n\n')
    else:
        file.write(f'pico_enable_stdio_usb({projectName} 0)\n\n')

    # If we need wireless, check for SSID and password
    # removed for the moment as these settings are currently only needed for the pico-examples
    # but may be required in here at a later date.
    if False:
        if 'ssid' in params or 'password' in params:
            file.write('# Add any wireless access point information\n')
            file.write(f'target_compile_definitions({projectName} PRIVATE\n')
            if 'ssid' in params:
                file.write(f'WIFI_SSID=\" {params["ssid"]} \"\n')
            else:
                file.write(f'WIFI_SSID=\"${WIFI_SSID}\"')

            if 'password' in params:
                file.write(f'WIFI_PASSWORD=\"{params["password"]}\"\n')
            else:
                file.write(f'WIFI_PASSWORD=\"${WIFI_PASSWORD}\"')
            file.write(')\n\n')

    # Standard libraries
    file.write('# Add the standard library to the build\n')
    file.write(f'target_link_libraries({projectName}\n')
    file.write("        " + STANDARD_LIBRARIES)
    file.write(')\n\n')

    # Standard include directories
    file.write('# Add the standard include files to the build\n')
    file.write(f'target_include_directories({projectName} PRIVATE\n')
    file.write("  ${CMAKE_CURRENT_LIST_DIR}\n")
    file.write("  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required\n")
    file.write(')\n\n')

    # Selected libraries/features
    if (params['features']):
        file.write('# Add any user requested libraries\n')
        file.write(f'target_link_libraries({projectName} \n')
        for feat in params['features']:
            if (feat in features_list):
                file.write("        " + features_list[feat][LIB_NAME] + '\n')
            if (feat in picow_options_list):
                file.write("        " + picow_options_list[feat][LIB_NAME] + '\n')
        file.write('        )\n\n')

    file.write(f'pico_add_extra_outputs({projectName})\n\n')

    file.close()


# Generates the requested project files, if any
def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger):

    oldCWD = os.getcwd()

    os.chdir(projectPath)

    deb = debugger_config_list[debugger]
    server_args = debug_server_args_list[debugger]

    for p in projects :
        if p == 'vscode':
            v1 = ('{\n'
                  '  // Use IntelliSense to learn about possible attributes.\n'
                  '  // Hover to view descriptions of existing attributes.\n'
                  '  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n'
                  '  "version": "0.2.0",\n'
                  '  "configurations": [\n'
                  '    {\n'
                  '      "name": "Cortex Debug",\n'
                  '      "cwd": "${workspaceRoot}",\n'
                  '      "executable": "${command:cmake.launchTargetPath}",\n'
                  '      "request": "launch",\n'
                  '      "type": "cortex-debug",\n'
                  '      "servertype": "openocd",\n'
                  '      "gdbPath": "gdb-multiarch",\n'
                  '      "serverArgs": [\n'
                  f'        {server_args}\n'
                  '      ],\n'
                  '      "device": "RP2040",\n'
                  '      "configFiles": [\n' + \
                  f'        "interface/{deb}",\n' + \
                  '        "target/rp2040.cfg"\n' + \
                  '        ],\n' +  \
                  '      "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",\n'
                  '      "runToEntryPoint": "main",\n'
                  '      // Give restart the same functionality as runToEntryPoint - main\n'
                  '      "postRestartCommands": [\n'
                  '          "break main",\n'
                  '          "continue"\n'
                  '      ]\n'
                  '    }\n'
                  '  ]\n'
                  '}\n')

            c1 = ('{\n'
                  '  "configurations": [\n'
                  '    {\n'
                  '      "name": "Linux",\n'
                  '      "includePath": [\n'
                  '        "${workspaceFolder}/**",\n'
                  '        "${env:PICO_SDK_PATH}/**"\n'
                  '      ],\n'
                  '      "defines": [],\n'
                  f'      "compilerPath": "{compilerPath}",\n'
                  '      "cStandard": "gnu17",\n'
                  '      "cppStandard": "gnu++14",\n'
                  '      "intelliSenseMode": "linux-gcc-arm",\n'
                  '      "configurationProvider" : "ms-vscode.cmake-tools"\n'
                  '    }\n'
                  '  ],\n'
                  '  "version": 4\n'
                  '}\n')

            s1 = ( '{\n'
                   '  "cmake.configureOnOpen": false,\n'
                   '  "cmake.statusbar.advanced": {\n'
                   '    "debug" : {\n'
                   '      "visibility": "hidden"\n'
                   '              },\n'
                   '    "launch" : {\n'
                   '      "visibility": "hidden"\n'
                   '               },\n'
                   '    "build" : {\n'
                   '      "visibility": "hidden"\n'
                   '               },\n'
                   '    "buildTarget" : {\n'
                   '      "visibility": "hidden"\n'
                   '               },\n'
                   '     },\n'
                   '}\n')

            e1 = ( '{\n'
                   '  "recommendations": [\n'
                   '    "marus25.cortex-debug",\n'
                   '    "ms-vscode.cmake-tools",\n'
                   '    "ms-vscode.cpptools"\n'
                   '  ]\n'
                   '}\n')

            # Create a build folder, and run our cmake project build from it
            if not os.path.exists(VSCODE_FOLDER):
                os.mkdir(VSCODE_FOLDER)

            os.chdir(VSCODE_FOLDER)

            filename = VSCODE_LAUNCH_FILENAME
            file = open(filename, 'w')
            file.write(v1)
            file.close()

            file = open(VSCODE_C_PROPERTIES_FILENAME, 'w')
            file.write(c1)
            file.close()

            file = open(VSCODE_SETTINGS_FILENAME, 'w')
            file.write(s1)
            file.close()

            file = open(VSCODE_EXTENSIONS_FILENAME, 'w')
            file.write(e1)
            file.close()

        else :
            print('Unknown project type requested')

    os.chdir(oldCWD)


def LoadConfigurations():
    try:
        with open(args.tsv) as tsvfile:
            reader = csv.DictReader(tsvfile, dialect='excel-tab')
            for row in reader:
                configuration_dictionary.append(row)
    except:
        print("No Pico configurations file found. Continuing without")

def LoadBoardTypes(sdkPath):
    # Scan the boards folder for all header files, extract filenames, and make a list of the results
    # default folder is <PICO_SDK_PATH>/src/boards/include/boards/*
    # If the PICO_BOARD_HEADER_DIRS environment variable is set, use that as well

    loc = sdkPath / "src/boards/include/boards"
    boards=[]
    for x in Path(loc).iterdir():
        if x.suffix == '.h':
            boards.append(x.stem)

    loc = os.getenv('PICO_BOARD_HEADER_DIRS')

    if loc != None:
        for x in Path(loc).iterdir():
            if x.suffix == '.h':
                boards.append(x.stem)

    return boards

def DoEverything(parent, params):

    if not os.path.exists(params['projectRoot']):
        if params['wantGUI']:
            mb.showerror('Raspberry Pi Pico Project Generator', 'Invalid project path. Select a valid path and try again')
            return
        else:
            print('Invalid project path')
            sys.exit(ExitCodes.INVALID_PROJECT_PATH)

    oldCWD = os.getcwd()
    os.chdir(params['projectRoot'])

    # Create our project folder as subfolder
    os.makedirs(params['projectName'], exist_ok=True)

    os.chdir(params['projectName'])

    projectPath = params['projectRoot'] / params['projectName']

    # First check if there is already a project in the folder
    # If there is we abort unless the overwrite flag it set
    if os.path.exists(CMAKELIST_FILENAME):
        if not params['wantOverwrite'] :
            if params['wantGUI']:
                # We can ask the user if they want to overwrite
                y = mb.askquestion('Raspberry Pi Pico Project Generator', 'There already appears to be a project in this folder. \nPress Yes to overwrite project files, or Cancel to chose another folder')
                if y != 'yes':
                    return
            else:
                print('There already appears to be a project in this folder. Use the --overwrite option to overwrite the existing project')
                sys.exit(ExitCodes.EXISTING_PROJECT_NO_OVERWRITE)

        # We should really confirm the user wants to overwrite
        #print('Are you sure you want to overwrite the existing project files? (y/N)')
        #c = input().split(" ")[0]
        #if c != 'y' and c != 'Y' :
        #    sys.exit(ExitCodes.SUCCESS)

    # Copy the SDK finder cmake file to our project folder
    # Can be found here <PICO_SDK_PATH>/external/pico_sdk_import.cmake
    shutil.copyfile(params['sdkPath'] / 'external' / 'pico_sdk_import.cmake', projectPath / 'pico_sdk_import.cmake' )

    if params['features']:
        features_and_examples = params['features'][:]
    else:
        features_and_examples= []

    if params['wantExamples']:
        features_and_examples = list(stdlib_examples_list.keys()) + features_and_examples

    GenerateMain('.', params['projectName'], features_and_examples, params['wantCPP'])

    GenerateCMake('.', params)

    # If we have any ancilliary files, copy them to our project folder
    # Currently only the picow with lwIP support needs an extra file, so just check that list
    for feat in features_and_examples:
        if feat in picow_options_list:
            if picow_options_list[feat][ANCILLARY_FILE] != "":
                shutil.copy(sourcefolder + "/" + picow_options_list[feat][ANCILLARY_FILE], projectPath / picow_options_list[feat][ANCILLARY_FILE])

    # Create a build folder, and run our cmake project build from it
    if not os.path.exists('build'):
        os.mkdir('build')

    os.chdir('build')

    # If we are overwriting a previous project, we should probably clear the folder, but that might delete something the users thinks is important, so
    # for the moment, just delete the CMakeCache.txt file as certain changes may need that to be recreated.

    if os.path.exists(CMAKECACHE_FILENAME):
        os.remove(CMAKECACHE_FILENAME)

    cpus = os.cpu_count()
    if cpus == None:
        cpus = 1

    if isWindows:
        # Had a special case report, when using MinGW, need to check if using nmake or mingw32-make.
        if shutil.which("mingw32-make"):
            # Assume MinGW environment
            cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" ..'
            makeCmd = 'mingw32-make '

        else:
            # Everything else assume nmake
            cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G "NMake Makefiles" ..'
            makeCmd = 'nmake '
    else:
        cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug ..'
        makeCmd = 'make -j' + str(cpus)

    if params['wantGUI']:
        RunCommandInWindow(parent, cmakeCmd)
    else:
        os.system(cmakeCmd)

    if params['projects']:
        generateProjectFiles(projectPath, params['projectName'], params['sdkPath'], params['projects'], params['debugger'])

    if params['wantBuild']:
        if params['wantGUI']:
            RunCommandInWindow(parent, makeCmd)
        else:
            os.system(makeCmd)
            print('\nIf the application has built correctly, you can now transfer it to the Raspberry Pi Pico board')

    os.chdir(oldCWD)


###################################################################################
# main execution starteth here

sourcefolder = os.path.dirname(os.path.abspath(__file__))

args = ParseCommandLine()

if args.nouart:
    args.uart = False

if args.debugger > len(debugger_list) - 1:
    args.debugger = 0

# Check we have everything we need to compile etc
c = CheckPrerequisites()

## TODO Do both warnings in the same error message so user does have to keep coming back to find still more to do

if c == None:
    m = f'Unable to find the `{COMPILER_NAME}` compiler\n'
    m +='You will need to install an appropriate compiler to build a Raspberry Pi Pico project\n'
    m += 'See the Raspberry Pi Pico documentation for how to do this on your particular platform\n'

    if (args.gui):
        RunWarning(m)
    else:
        print(m)
    sys.exit(ExitCodes.NO_COMPILER_FOUND)

if args.name == None and not args.gui and not args.list and not args.configs and not args.boardlist:
    print("No project name specfied\n")
    sys.exit(ExitCodes.NO_PROJECT_NAME)

# Check if we were provided a compiler path, and override the default if so
if args.cpath:
    compilerPath = Path(args.cpath)
else:
    compilerPath = Path(c)

# load/parse any configuration dictionary we may have
LoadConfigurations()

p = CheckSDKPath(args.gui)

if p == None:
    sys.exit(ExitCodes.PICO_SDK_NOT_FOUND)

sdkPath = Path(p)

boardtype_list = LoadBoardTypes(sdkPath)
boardtype_list.sort()

if args.gui:
    RunGUI(sdkPath, args) # does not return, only exits

projectRoot = Path(os.getcwd()) if not args.projectRoot else Path(args.projectRoot)

if args.list or args.configs or args.boardlist:
    if args.list:
        print("Available project features:\n")
        for feat in features_list:
            print(feat.ljust(6), '\t', features_list[feat][GUI_TEXT])
        print('\n')

    if args.configs:
        print("Available project configuration items:\n")
        for conf in configuration_dictionary:
            print(conf['name'].ljust(40), '\t', conf['description'])
        print('\n')

    if args.boardlist:
        print("Available board types:\n")
        for board in boardtype_list:
            print(board)
        print('\n')

    sys.exit(ExitCodes.SUCCESS)
else :
    params={
        'sdkPath'       : sdkPath,
        'projectRoot'   : projectRoot,
        'projectName'   : args.name,
        'wantGUI'       : False,
        'wantOverwrite' : args.overwrite,
        'boardtype'     : args.boardtype,
        'wantBuild'     : args.build,
        'features'      : args.feature,
        'projects'      : args.project,
        'configs'       : (),
        'wantRunFromRAM': args.runFromRAM,
        'wantExamples'  : args.examples,
        'wantUART'      : args.uart,
        'wantUSB'       : args.usb,
        'wantCPP'       : args.cpp,
        'debugger'      : args.debugger,
        'exceptions'    : args.cppexceptions,
        'rtti'          : args.cpprtti,
        'ssid'          : '',
        'password'      : '',
        }

    DoEverything(None, params)
